package edu.psu.geovista.app.pcp.plot.control.tools;

import edu.psu.geovista.app.pcp.DfPcp;
import edu.psu.geovista.app.pcp.Pcp;
import edu.psu.geovista.app.pcp.animation.AnimationResponder;
import edu.psu.geovista.app.pcp.event.PcpSelectionEvent;
import edu.psu.geovista.app.pcp.plot.control.ActionMgr;
import edu.psu.geovista.app.pcp.plot.gui.BasicPcpUI;
import edu.psu.geovista.app.pcp.plot.layer.DataLayer;
import edu.psu.geovista.app.pcp.plot.layer.DataPainter;
import edu.psu.geovista.app.pcp.plot.layer.DynamicLayer;
import edu.psu.geovista.app.pcp.plot.services.CommonService;
import edu.psu.geovista.app.pcp.toolkits.Summarizer;
import edu.psu.geovista.app.pcp.toolkits.collection.CollectionUtils;
import edu.psu.geovista.app.pcp.toolkits.group.DataIDGroup;
import edu.psu.geovista.app.pcp.toolkits.group.DataIDSet;
import edu.psu.geovista.app.plot.axis.AxisList;
import edu.psu.geovista.app.plot.axis.YAxis;
import edu.psu.geovista.app.plot.layer.Layer;
import edu.psu.geovista.classification.CategoryItf;
import edu.psu.geovista.classification.ClassificationResultItf;
import edu.psu.geovista.data.model.AppDataModel;
import edu.psu.geovista.data.model.ColumnList;
import edu.psu.geovista.ui.event.FreezeEvent;
import edu.psu.geovista.ui.event.SelectionEvent;
import edu.psu.geovista.ui.event.SelectionListener;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.util.*;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
* GeoVISTA Center (Penn State, Dept. of Geography)
* Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
* All Rights Researved.
*
* Most
* @author: jin Chen
* @date: Jun 30, 2003
* @version: $VERSION$
*/

public class DataSelectTool extends BasicMouseTool
        implements AnimationResponder{
    public static final int MODE_Normal=0; //must use 0, it is a default value if no value is assigned
    public static final int MODE_CategorySummary=1;
    public static final int MODE_ObservationSummary=2;//e.g: state summary

   // private static final String pkgname=DataSelectTool.class.getPackage().getName();


    private Point startp;
    private HashSet lastSelection=new HashSet(0);  // 
    private HashSet curSelection=new HashSet(0);  //selection of current brushing session
    private  HashSet allselection =new HashSet(); //total selection
    private Set frozenIds=new HashSet(0);//frozen observations id
    private boolean needClearSelection=false;
    protected EventListenerList listenerList = new EventListenerList();

    private int mode=MODE_Normal;//temp

    //protected SelectionEventFirer seFirer=new SelectionEventFirer();
    protected ColorArrayEventFirer colorFirer=new ColorArrayEventFirer();
    protected String obSummaryVar=null;// 

    protected boolean hightLightMode=true;
    protected boolean stopActOnMouse=false; // 
    protected boolean smartHighlight=true;// 

    private int highlightId=-1;// 

    DataModeActor mactor=null;
    Map mactors=new HashMap(3);

    private static final Logger logger=Logger.getLogger(DataSelectTool.class.getName() );
    static {
        //set up logger
        Level mylevel= Level.WARNING;
          ConsoleHandler handler=new ConsoleHandler();
          logger.addHandler(handler);  // 
       
          logger.setLevel(mylevel);
          handler.setLevel(mylevel );
    }

    public DataSelectTool(BasicPcpUI ui) {
        super(ui);
        startp=new Point();

        DataModeActor actor=new NormalDModeActor(this) ;
        mactors.put(new Integer(MODE_Normal),actor);
        mactor=actor;

        actor=new CategorySumDModeActor(this) ;
        mactors.put(new Integer(MODE_CategorySummary),actor);
        actor=new ObsrSumDModeActor(this) ;
        mactors.put(new Integer(MODE_ObservationSummary),actor);




        //myDataModel=this.getUI().getPlot().getDataModel();
    }
     /*******************************************************************************************************
     *                highligth
     *******************************************************************************************************/
    /**
     *
     * @param id
     * @param x
     * @param y
     */
    public void processIndication(int id,int x, int y) {
        
       //  check to make sure id within current datamodel ids range
        AppDataModel dataModel = this.getDataModel();
        int numRecords = dataModel.getRowCount();
        if(id<0||id>=numRecords)return;

        if(this.getMode()==MODE_Normal){//if turn off indication, won't act on external inidcation event
            this.mactor.processIndication(id,x,y);
        }

    }

    public void setHighLightMode(boolean hightLightMode) {
        this.hightLightMode = hightLightMode;
        if(!hightLightMode){
            this.hightlightNothing();
        }

    }

    public  void hightlightNothing(){
        this.setHighlightId(-1);
        this.highLightData(-1,10,10);
    }
    public void highLightData(int id,int x, int y) {

        DynamicLayer tlyr= (DynamicLayer) this.ui.getLayer(Layer.DYNAMIC_LAYER );


        String rowName="";
        try {
            if(id>=0){// 
                rowName = this.getService().getObservationName(id);

            }
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
            // 
        }
        tlyr.highlightLine(rowName,id,x,y);// 
        logger.fine("Highlight "+id);
        this.repaintPlot();

    }
    /*******************************************************************************************************
     *                freeze
     *******************************************************************************************************/
    public void processFreeze(FreezeEvent e){
        this.mactor.processFreeze(e);
    }
     public void freezeData(Set ids,boolean fireEvent){
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        srLayer.setFrozenIds(ids);

        srLayer.clearLayer(); // 
        srLayer.paintLayer() ;
        repaintPlot() ;
        //this.fi
         if(fireEvent){
             BasicPcpUI ui = getUI();
            Pcp plot = ui.getPlot();
            int[] fzs = CollectionUtils.convertToIntArray(frozenIds);// //todo only work for normal mode, for other mode, need convert ids
             FreezeEvent e=new FreezeEvent(this,this.getMode() ,fzs); //source should be PCP, here just set it null
            ((DfPcp)plot).fireFreezeChanged(e);
         }

    }
    /*******************************************************************************************************
     *                selection
     *******************************************************************************************************/
     /**
     * show only selected data with id belong to ids. Selected data can be mouse selection, animation or
     * selection from external component. It will send ONLY DataGroup.TYPE_SelectData to showRangeDataLayer.
     * @param ids           collection contain all id of records to be shown
     * @param repaintPlot   true,repaintplot right away at the end of the method
     * @param fireEvent     if true, fire selectionChange event, which is to inform outside(other beans)
     * @param renew          
     */
    public void selectData(HashSet ids,boolean repaintPlot,boolean fireEvent,boolean renew) {

        if (ids.size() <getUI().getPlot().getDataModel().getRowCount() ){
            DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.BgData_LAYER );
            if(dataLayer!=null)
                dataLayer.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER ,DataLayer.DataBackgroundLightness) );
        }
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        /**
         *   
         *  After selecting is done, the entire selection(lastSelection) should be persisted by srLayer
         *  thus selection will be kept when other operations(say, re-order axis) is going on
         *
         *  Entire Selection is persisted by mouseReleased()
         */
        srLayer.setRecordsToShow(new DataIDSet(ids,DataIDSet.TYPE_SelectData ) );
        srLayer.setVisible(true);
        //if (this.isRenewSelection() )
        if(renew)
            srLayer.clearLayer() ; //
        srLayer.paintLayer() ;
        if(repaintPlot)
            this.repaintPlot() ;
        if(fireEvent){
            this.fireSelectionChange(ids );
            //this.fireSelectionChange(this.lastSelection );
        }
    }



    public  void clearDynamicLayer(boolean repaintPlot) {
        DynamicLayer dynLayer=(DynamicLayer) this.ui.getLayer(Layer.DYNAMIC_LAYER );
        dynLayer.clearLayer() ;//Before find new one, won't clean
        if(repaintPlot)
            this.repaintPlot() ;
    }
    /*******************************************************************************************************
     *                handle animation
     *******************************************************************************************************/

    public void aboutToStartNewSessoin() {
        clearDataLayer();
    }

    public void doAccumulativeAnimation(float dataId) {
        showAnimationData((int)dataId,false);
    }

    public void doOneByOneAnimation(float dataId) {
        showAnimationData((int)dataId,true);
    }

    /*******************************************************************************************************
     *                Handle mouse selecting data
     *******************************************************************************************************/
    public void mouseClicked(MouseEvent e) {
        int button = e.getButton();
        //System.out.println("click button:"+button);
        if(button==MouseEvent.BUTTON3){  //right-click show menu
            //right click

            JPopupMenu popm = getPopupMenu();
            ui.showPopupMenu(popm, e.getX() ,e.getY() );


        }
    }

    private JPopupMenu getPopupMenu() {
        //this.setHightLightMode(false);
        ActionMgr actionMgr = this.ui.getActionMgr();
        //indication
        Action actHl = actionMgr.getAction(ActionMgr.ACTION_HIGHLIGHT);
        //ActionEvent ae=new ActionEvent(null,0,(String) hl.getValue(Action.ACTION_COMMAND_KEY));
        //hl.actionPerformed(null);
        String hls=null;
        if(this.isHightLightMode() ){
            hls="Disable indication";//"Freeze the hightlighed line";
        }
        else{
            hls="Enable indication";
        }
        JMenuItem itemHL=new JMenuItem(hls);
        itemHL.addActionListener(actHl);// not use setAction(actHl) since not want to use its icon and text

        Action freezeAction =this.getFreezeAction();
        JButton highlight = actionMgr.getButton(ActionMgr.ACTION_HIGHLIGHT);
        if(highlight.isSelected() ) {
            freezeAction.setEnabled(true);
        }
        else{
            freezeAction.setEnabled(false);
        }

        Action unfreeze = this.getUnFreezeAction();
        Action unfrzall = this.getUnFreezeAllAction();
        //Freeze items
        JMenuItem itemFreeze=new JMenuItem();//Brush the hightlighed string");
        itemFreeze.setAction(freezeAction);// .addActionListener(brush);
         //UnFreeze items
        JMenuItem itemUnfreeze=new JMenuItem();//Brush the hightlighed string");
        itemUnfreeze.setAction(unfreeze);// .addActionListener(brush);
         //Freeze items
        JMenuItem itemFrzAll=new JMenuItem();//Brush the hightlighed string");
        itemFrzAll.setAction(unfrzall);// .addActionListener(brush);

        JPopupMenu popm=new JPopupMenu();
        popm.add(itemFreeze);
        popm.add(itemUnfreeze);
        popm.add(itemFrzAll);
        popm.add(new JPopupMenu.Separator() );
        popm.add(itemHL);

      /*  //screen shoot
        Action sshotAction=new AbstractAction(){
            public void actionPerformed(ActionEvent e) {
               // DfPcp plot = (DfPcp) getUI().getPlot();
                BasicPcpUI ui = getUI();
                PcpCanvas plotCanvas = ui.getPlotCanvas();
                CopyPastor..screenShoot((JComponent)plotCanvas);
            }

        };
        sshotAction.putValue(Action.NAME,"Screenshot");
        JMenuItem sshootMenuItem=new JMenuItem("Screenshot");
        sshootMenuItem.setAction(sshotAction);
        sshootMenuItem.setText("Screenshot");*/
        //popm.add("");

       /* popm.add(new JPopupMenu.Separator() );
        popm.add(sshootMenuItem);*/
        return popm;
    }

    private Action getFreezeAction(){
        //if(this.brushAction ==null){
        Action freezeAction =new AbstractAction(){

            public void actionPerformed(ActionEvent e) {
                final int highlightId = getHighlightId();
                if(highlightId>=0){
                    //there is a highlight
                   /* clearSelection();
                    lastSelection.add(new Integer(highlightId));
                    selectData(lastSelection,true,true,true);*/

                   frozenIds.add(new Integer(highlightId));
                    boolean fireEvent = true;
                    freezeData(frozenIds,fireEvent);
                }

            }
        } ;
        freezeAction.putValue(Action.NAME,"Freeze this line");
        //}
        return freezeAction;
    }
     private Action getUnFreezeAction(){
        //if(this.brushAction ==null){
        Action freezeAction =new AbstractAction(){

            public void actionPerformed(ActionEvent e) {
                final int highlightId = getHighlightId();
                if(highlightId>=0){
                    //there is a highlight
                    /*clearSelection();
                    lastSelection.add(new Integer(highlightId));
                    showSelectedDataWithIds(lastSelection,true,true,true);*/
                    frozenIds.remove(new Integer(highlightId));
                    boolean fireEvent = true;
                    freezeData(frozenIds,true);
                }

            }
        } ;
        freezeAction.putValue(Action.NAME,"Unfreeze this line");
        //}
        return freezeAction;
    }
     private Action getUnFreezeAllAction(){

        Action freezeAction =new AbstractAction(){

            public void actionPerformed(ActionEvent e) {
                    frozenIds.clear();
                    boolean fireEvent = true;
                    freezeData(frozenIds,fireEvent);
            }
        } ;
        freezeAction.putValue(Action.NAME,"Unfreeze all");

        return freezeAction;
    }

    public void mousePressed(MouseEvent e) {
        if(this.isStopActOnMouse() )return;
        int button = e.getButton();
        System.out.println("click button:"+button);
        if(e.isShiftDown() ){//add another selection
             logger.fine("shiftDown");
        }
        else if (e.isAltDown() ){//substract selection
             logger.fine("altDown");
        }
        else if (e.isControlDown()){
             logger.fine("controlDown");
        }
        else if(SwingUtilities.isLeftMouseButton(e)){//  
            //boolean leftMouseButton = SwingUtilities.isLeftMouseButton(e);
            //button==MouseEvent.BUTTON1 || button ==MouseEvent.NOBUTTON


            setNeedClearSelection(true); // 
            this.setToolOn(true);


        }
        this.updateLCpoint(e.getPoint()) ;


    }
    public void mouseDragged(MouseEvent e) {
        if(this.isStopActOnMouse() )return;
        //??? need restrict brushing's cover space NOT beyond a axis's gap
        //System.out.println("test");

        this.updateLCpoint(e.getPoint()) ;
        /* if(DEBUG){
        System.out.println("brushing:"+lastp.getX() +","+lastp.getY() +" to "+cp.getX()+", "+cp.getY() );
        }*/

        //draw brushing line
        DynamicLayer dl=(DynamicLayer)ui.getLayer(Layer.DYNAMIC_LAYER );
        dl.drawBrushing(this.lastp,this.cp );


        int index=this.getAxisDisplayIndex(e.getX() );
        //System.out.println(" brush right to axis:"+index);
        if (index<0){
            //brushing outside all axis
            logger.fine("brushing outside all axis. Nothing selected. ");
            return;
        }
        Line2D.Float brushLine=new Line2D.Float(this.lastp,this.cp );
        HashSet curSelect=this.getBrushedRecord(null, index,index+1,brushLine);
        if (curSelect.size() >0){ //
            logger.fine("brushing "+curSelect.size()+" records ");
            if(this.isNeedClearSelection() ){
                logger.fine("clear last selection to make new brushing sesssion ");
                // 
                //  
                // 
                //clear selection of previous brushing
                this.clearSelection() ;
                //clear plot of previous brushing
                DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
                srLayer.clearLayer() ;
                this.setNeedClearSelection(false);
            }
            HashSet lastSel=this.getCopyOfLastSelection() ; //copy of lastSelection
            if(e.isAltDown() ){//substract selection
                logger.fine(" ALT: make subtract selection ");
                if(lastSel!=null){
                    //lastSel.removeAll(curSelect);


                    lastSel.removeAll(curSelect);  ////this.substractSet(curSelect,lastSel );
                    //this.setRenewSelection(true);//remove previous selection, paint only the new selection
                    //this.showDataWithIds(lastSel,false);
                    this.selectData(lastSel,true,false,true);//don't fire event externally while dragging. Only fire it upon releasing
                }
            }
            else if (e.isControlDown()){// intersection
                logger.fine(" Control down: make intersection selection ");
                if(lastSel!=null){
                    //if(tmpshow.s)
                    this.curSelection.addAll(curSelect);

                    allselection.addAll(curSelection);
                    allselection.addAll(lastSelection); //during the dragging, just draw all selections made previously and currently, and compute intersection when finally release
                     //this.curSelection.retainAll(this.getLastSelection() );
                    //allselection.r

                    if(lastSel.size()>0)
                     this.selectData(allselection,true,false,true); //
                    //System.out.println("lastSel size()"+lastSel.size());
                }
            }
            else {//  
                //this.setRenewSelection(false);//paint current selection on top of existing ones
                //keep track all the selection
                if(lastSel!=null )  {     //e.isShiftDown()&&//
                    logger.fine(" make adding selection ");
                    lastSel.addAll(curSelect);
                    //this.mergeSet(curSelect,lastSel );
                }
                else{
                    logger.fine(" make normal selection ");
                    lastSel=curSelect;
                }
                /**
                 * don't fire event externally while dragging. Only fire it upon releasing
                 * need set renew true to avoid a bug ( initial selection not work since curSelect.size()=0, not clear datalayer)
                 * Brushing performance is not a concern, more important is make code simple and less buggy ( make sure what selected match to what in selectionEvent)
                 */
                 this.selectData(lastSel,true,false,true);

            }

            logger.info(" the final selection: "+lastSel.size() );
            this.setLastSelection(lastSel);
        }
        this.repaintPlot() ;//paint brushing line



    }
    public void mouseReleased(MouseEvent e) {
        if(this.isStopActOnMouse() )return;
        if(e.getButton()==MouseEvent.BUTTON1){//left clicking
            super.mouseReleased(e);
            DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
            // 
            HashSet ids=this.getLastSelection() ;
            if(e.isControlDown() ){
                //HashSet intersect=new HashSet();
                this.curSelection.retainAll(this.getLastSelection() );
                if(curSelection.size() >0){
                    ids=curSelection;
                    this.setLastSelection(curSelection);
                    this.selectData(ids,true,false,true);
                }

            }
            srLayer.setRecordsToShow(new DataIDSet(ids,DataIDSet.TYPE_SelectData ) );
            //<-

            this.fireSelectionChangedInternally() ;
            this.fireSelectionChange(ids );

            curSelection.clear();// 
            allselection.clear();// 
        }
    }
    //For highlight line only
    public void mouseMoved(MouseEvent e) {

        boolean hlm = this.isHightLightMode();
        if(this.isStopActOnMouse()||! hlm ){
            //System.out.println("highlight:"+false);
            return;
        }
        //System.out.println("highlight:"+true);
        this.updateLCpoint(e.getPoint()) ;

        int index=this.getAxisDisplayIndex(e.getX() );
        //System.out.println(" brush right to axis:"+index);
        if (index<0){
            //brushing outside all axis
            return;
        }
        Line2D.Float brushLine=new Line2D.Float(this.lastp,this.cp );

        HashSet curSelect;
        HashSet lastSelection=this.getLastSelection() ;
        if (!this.isSmartHighlight() ||(lastSelection==null ||lastSelection.size() ==0)) {
            //if lastSelection contain nothing, select from whole data set
            curSelect = this.getBrushedRecord(null , index,index+1,brushLine); //in whole data domain
        } else { // User have specified selection domain(=lastSelectoin), only make selection out of the domain
            curSelect = this.getBrushedRecord(lastSelection , index,index+1,brushLine);
        }
        /*if(curSelect==null||curSelect.size() ==0){
        curSelect=this.getBrushedRecord(null , index,index+1,brushLine);
        }*/
        Object[] ids=curSelect.toArray() ;    // 
        if(ids==null||ids.length <=0) {//highlight nothing
            //this.highlightId=-1;
            return;
        }

        //int id =-1;
        // 
        // 
        /*for (int i=0;i<ids.length ;i++){
        if(this.lastSelection.contains(ids[i])){
        id =((Integer)ids[i]).intValue() ;
        break;
        }
        }*/
        // 
        // if(id==-1)
        int   id =((Integer)ids[0]).intValue() ;// 
        setHighlightId(id);
        int x=e.getX() ;int y=e.getY() ;
        this.highLightData(id,x,y);
        this.fireIndicationChange(id);
    }
    /*******************************************************************************************************
     *                event
     *******************************************************************************************************/

    /**
     * adds an internal SelectionListener. It is for internal usage.
     * SelectionEvent fired externally may not be caused by selecting action, it can also be caused by indication
     * While SelectionEvent fired internally is guaranteed caused by user selecting action
     */
    public void addInternalSelectionListener (SelectionListener l) {
        listenerList.add(SelectionListener.class, l);

    }

    /**
     * removes an SelectionListener from the component
     */
    public void removeInternalSelectionListener (SelectionListener l) {
        listenerList.remove(SelectionListener.class, l);

    }


    void fireIndicationChange(int id) {
        this.mactor.fireIndicationChange(id);
        /* if(this.getMode() ==MODE_Normal)  {
        //Currently, indication event can only carry one id. However, in summary mode, multiple id need to be sent
        //For now, just turn off indication on summary mode
        this.getUI().getPlot().fireIndicationChanged(id);
        }
        else if(this.getMode() ==MODE_ObservationSummary) {
        //Since Map and Scatter plot accept ONLY single-record indication, while indication in summary mode contain multiple records
        // thus we use selection event to serve the purpose

        //this.lastSelection.clear() ;
        //this.clearSelection() ;    //wrong, select,any indicate, it clear last selection, but the selection is still on the screen.
        Integer indicateId=new Integer(id);
        //this.lastSelection.add(indicateId);
        HashSet ids = new HashSet();
        ids.add(indicateId);
        this.fireSelectionChange(ids ) ;
        //this.clearSelection() ;   //temp solution, since there is only indication, no actually selection

        }*/

    }
    public void fireSelectionChange(HashSet Ids) {
        //this.seFirer.fireSelectionChange(ids) ;
        PcpSelectionEvent evt=null;
        final int[] ids = this.mactor.getMemberIds(Ids);
        evt=new PcpSelectionEvent(this,ids);

            evt.setMode(getMode());
            evt.setObSummaryVar( DataSelectTool.this.obSummaryVar );
            //getUI().getPlot().tryFireSelectionChanged(ids, getMode());
            getUI().getPlot().fireSelectionChanged(evt);

    }
      /**
     * It does not contain informaton about selection. you need obtain it from the event source
     */
    public  void fireSelectionChangedInternally () {

        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        PcpSelectionEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == SelectionListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new PcpSelectionEvent(this, new int[0]);
                    e.setMode(mode);
                }
                ((SelectionListener)listeners[i + 1]).selectionChanged(e);
            }
        }//next i

    }
    /*******************************************************************************************************
     *                support
     *******************************************************************************************************/

    /**
     * add curSelect to lastSelection
     * @param curSelect
     * @param lastSelection
     */
    private void mergeSet(HashSet curSelect, HashSet lastSelection) {
        Iterator iter=curSelect.iterator() ;
        while(iter.hasNext()){
            Object o = iter.next();
            lastSelection.add(o);

        }
    }

    private void substractSet(HashSet curSelect, HashSet lastSelection) {
        Iterator iter=curSelect.iterator() ;
        while(iter.hasNext()){
            Object o = iter.next();
            lastSelection.remove(o);

        }
    }
    private void intersection(HashSet curSelect, HashSet lastSelection) {

    }

    /**
     * Assume brushing happen between 2 axis
     * @param from  (display) index of axis on left
     * @param to    (display) index of axis on right
     * @param line  brushing line
     * @return       record ID of all record that is selected ( intersected by brushing line)
     */
    private HashSet getBrushedRecord(Set domain, int from, int to, Line2D.Float line ){
        AxisList yaxisList=this.getUI().getPlot().getYaxisList() ;
        YAxis axFrm = yaxisList.getAxis(from);
        YAxis axTo=yaxisList.getAxis(to);
        return this.getService().getBrushedRecord(domain, axFrm,axTo,line);


    }
    /**
     * Assume brushing won't intersect more than 2 axis
     * @return  the displayIndex of axis that is exactly left to the brushing line
     */
    private int getAxisDisplayIndex(int x) {
        final AxisList yaxisList = getUI().getPlot().getYaxisList();
        Object[] axisList=yaxisList.getIndexSortAxisList().toArray() ;
        int index=0;
        for (int i=0;i<axisList.length ;i++){
            if (i==axisList.length-1){//outside all axis
                return -1;

            }
            else{
                YAxis ax1=(YAxis) axisList[i];
                YAxis ax2=(YAxis) axisList[i+1];
                index=ax1.getIndex() ;
                //System.out.println(","+index );
                if (ax1.getLocation().x<=x &&x<= ax2.getLocation().x){
                    //  x is between ax1 and ax2
                    return index;
                }
            }

        }
        return -1;


    }



    /*******************************************************************************************************
     *                Handle change categories visibility and Colors
     *******************************************************************************************************/

    /**
     *
     * @param id
     * @param renew   true, restart a new session of animation. False, continue
     * @return     false: unable to do animation
     */
    public boolean showAnimationData(int id, boolean renew) {
        /*if(this.getMode()==MODE_CategorySummary ){  //now we can do it
        Component c=this.getUI().getPlot() ;
        JOptionPane.showMessageDialog(c,"Unable to do animation in category summary mode. Please turn off the mode.") ;
        return false;

        }     */
        Integer e=new Integer(id);
        HashSet set=new HashSet();
        set.add(e);
        if(renew){
            //this.lastSelection.clear() ;
            this.clearSelection() ;
            addToLastSelection(e);
            //lastSelection.add(e); //for fireSelectionEvent
        }
        else{
            addToLastSelection(e);
            //lastSelection.add(e);//for fireSelectionEvent

        }
        // 
        this.selectData(set,true,true,renew );

        //highlight the last selected one
        this.highLightData(id,10,10);
        this.fireIndicationChange(id); //inform other components

        return true;
    }

    /**
     * To act on selection from external components(e.g. Map bean, ScatterPlot bean)
     *  A definitely renew selection

     */
    /* public void showDataWithIds(HashSet ids, boolean fireEvent){

    //Selection event from outside contain record. DataSelectCmd in summaryMode only show summarized data id.
    this.showSelectedDataWithIds(ids,true,fireEvent,true);


    }*/
    public void processSelection(SelectionEvent e) {
        mactor.processSelection(e);
       /* int[] ids=e.getSelection() ;
        if(ids.length<=0) return;

        if(e instanceof PcpSelectionEvent){ // event sent from other pcp(e.g.: time series plot...)
            PcpSelectionEvent mye=((PcpSelectionEvent)e);
            int mode=mye.getMode() ;

            if(this.getMode()==MODE_Normal){
                upAndShowSelection(ids);;
                //this.showSelectedDataWithIds(DataIDSet.convertArrayToSet(ids),true,false,true);
            }
            else if(this.getMode()==mode&&mode==MODE_ObservationSummary){
                //todo check summary variable is same
                String obSummaryVar=mye.getObSummaryVar() ;
                if(obSummaryVar!=null&& obSummaryVar.equals(this.obSummaryVar ) ){
                    //make sure PCP and TS use same variable to make observation summary
                    ids=mye.getModeIds() ;
                    if(ids!=null){
                        if(ids.length ==1) {
                            this.hightLightDataWithIds(ids[0],10,10);
                        }
                        else{
                            upAndShowSelection(ids);
                            //this.showSelectedDataWithIds(DataIDSet.convertArrayToSet(ids),true,false,true);
                        }
                    }
                }
            }


        }
        else{ // event sent from components other than pcp(e.g.: map...)
            if(getMode()==MODE_Normal){
                //Selection event from outside contain record. DataSelectCmd in summaryMode only show summarized data id.
                upAndShowSelection(ids);
                //this.showSelectedDataWithIds(DataIDSet.convertArrayToSet(ids),true,false,true);
            }
        }*/
    }
    /**
     * update lastSelection=ids and show it
     * Usually called by external selection event
     * @param ids
     */
    void upDateAndShowSelection(int[] ids){
        HashSet idSet=CollectionUtils.convertArrayToSet(ids);
        this.setLastSelection(idSet);
        this.selectData(idSet,true,false,true);
    }




    /**
     *  show only data within the DataGroup. It will repaint layer and plot but won't fire any selection event.
     *  Usually it is used for showing category data
     * @param ids
     *
     */
    void showDataGroup(DataIDSet ids) {
        if (ids.size() <getUI().getPlot().getDataModel().getRowCount() ){
            DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.BgData_LAYER );
            dataLayer.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER ,DataLayer.DataBackgroundLightness) );
        }
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        if (ids!=null){

            srLayer.setRecordsToShow( ids);
            srLayer.setVisible(true);
            srLayer.clearLayer() ; //
        }
        srLayer.paintLayer() ;


        this.repaintPlot() ;

    }

    public void clearDataLayer(){
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        srLayer.clearLayer() ;
        clearSelection();
        this.repaintPlot() ;
    }
    void clearSelection(){
        this.lastSelection.clear() ;
    }
    /**
     * Repaint showRangeDataLayer
     */
    public void repaintDataLayer(boolean show){
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        srLayer.updateRecordsToShow();
        srLayer.setVisible(show);//must
        srLayer.clearLayer() ;
        srLayer.paintLayer() ;
        this.repaintPlot() ;

    }
    public void reverseShowRangedData(){
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        boolean show=!srLayer.isVisible();
        repaintDataLayer(show);


    }

    /**
     *  show all data as background
     * @param show
     */
    public void showBackgroundData(boolean show) {
        DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.BgData_LAYER);
        if (show){
            dataLayer.setVisible(true);
        }
        else{
            dataLayer.setVisible(false);

        }
        this.repaintPlot() ;
    }
    /**
     * reverse the showInvalidData attribute of each Datalayer
     * One call will show on invalid data, another call will show off invalid.
     */
    public void reverseShowInvalidData() {
        DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.BgData_LAYER);
        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );
        //RuntimeLayer dyLayer=(DataLayer)this.ui.getLayer(Layer.DYNAMIC_LAYER );

        dataLayer.setInvalidDataShown(!dataLayer.isInvalidDataShown() );
        dataLayer.repaintLayer() ;
        srLayer.setInvalidDataShown(!srLayer.isInvalidDataShown() );
        srLayer.repaintLayer() ;
         this.repaintPlot() ;
    }
    public void updateShowBackgroundData() {
        DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.BgData_LAYER);
        boolean visible=dataLayer.isVisible() ;
        this.showBackgroundData(!visible);
    }

    /*******************************************************************************************************
     *                Summarize category
     *******************************************************************************************************/
    /**
     *  
     *  
     *  
     *  
     *  
     *  
     *  
     *  
     *
     *  
     * @return true if switch mode successful
     */
    private boolean swichMode(){

        this.setStopActOnMouse(true);    // 
        AppDataModel dataModel=(AppDataModel) this.getUI().getPlot().getDataModel() ;
        final int mode = this.getMode();

        if(this.getMode() ==MODE_ObservationSummary){//ask user based on what variable the summary is made.
                  String var=whatVariableToSummarize(dataModel);
                  if(var==null||var.equalsIgnoreCase("cancel") ){
                      setStopActOnMouse(false);  // 
                      return false; //user cancel the summary mode, --> no switch mode going on
                  }
                  else{
                     this.mactor =(DataModeActor) this.mactors.get(new Integer(mode));
                      final ObsrSumDModeActor mactor = ((ObsrSumDModeActor)this.mactor);
                      mactor.setObSummaryVar(var);
                      mactor.updateOnSwitchMode();
                      setStopActOnMouse(false);  // 
                     return true;
                  }
        }
        else{//normal mode or category(median) summary

            this.mactor =(DataModeActor) this.mactors.get(new Integer(mode));
            this.mactor.updateOnSwitchMode();
            setStopActOnMouse(false);// 
             return true;
        }
    }
    /**
     *
     * @param dataModel
     * @return       if return null, means no variable available for selection
     */
    String whatVariableToSummarize(AppDataModel dataModel){
        ColumnList columns=dataModel.getNonNumericColumns() ;
        if(columns==null||columns.size() ==0) return null;
        String[] names=new String[columns.size()];
       /* for (int i=0;i<columns.size() ;i++){
            Column col=(Column) columns.get(i);
            names[i]=col .getName() ;
            System.out.println("column:"+col.getName() );
        }*/

        JList list=new JList(names);
        JScrollPane scp=new JScrollPane(list);
        JLabel label=new JLabel("Please select a variable to summary.");
        JPanel pnl=new JPanel();
        pnl.setLayout(new BorderLayout());
        pnl.add(label,BorderLayout.NORTH );
        pnl.add(scp);

        Pcp pcp=this.getUI().getPlot() ;
        Object[] btns=new String[]{"Summary","Cancel"};
        int value=JOptionPane.showOptionDialog(((JComponent)pcp),pnl,"Please select a variable",
                JOptionPane.YES_NO_OPTION,
                JOptionPane.PLAIN_MESSAGE ,
                null,
                btns,
                btns[0]);
        if(value==JOptionPane.OK_OPTION ){
            //JOptionPane.OK_OPTION map to  "Summary" button
            String selectedValue = (String) list.getSelectedValue();
            System.out.println("value:"+selectedValue );
            return  selectedValue;

        }
        else if(value==JOptionPane.NO_OPTION||value==JOptionPane.CLOSED_OPTION ){
            //JOptionPane.NO_OPTION map to "Cancel" button
            System.out.println("cancel");
            return "cancel";
        }

        return null;
    }
    void updateServiceDataModel(AppDataModel dataModel){
        DynamicLayer dyLayer=(DynamicLayer)this.ui.getLayer(Layer.DYNAMIC_LAYER );   ////todo temp remove to avoid classcast exception
        //dyLayer.setDataModel(dataModel);       // 

        // 
        //  
        this.hightlightNothing();
        
        // 
        DataPainter painter = ui.getPainter();
        painter.setDataModel(dataModel);

        DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.TopData_LAYER );

        CommonService service=this.getService() ;
        srLayer.setDataModel(dataModel);

        service.setDataModel(dataModel);
        service.updateBoxplotForAllAxis(this.getAxisList());



    }


    /**
     * DataSelectCmd do not listen to event result from classifying (ColorArrayEvent)
     * Instead, it get the event indectly from CategoryCfg. The path: CategoryCfg=>DataConfig=>actOnClassify()
     */
    public void processClassification(ClassificationResultItf cr) {
        /*if (this.getMode()== MODE_CategorySummary) {
            CategoryItf[] ctgs =  cr.getCategories() ;
            updateModelForCategoryChange(ctgs); //need update model
            this.showData() ;
        }*/
        mactor.processClassification(cr);
        //this.colorFirer.fireColorArrayEvent();
    }


    void updateModelForCategoryChange(CategoryItf[] ctgs) {
        this.setStopActOnMouse(true);
        AppDataModel dataModel=(AppDataModel) this.getUI().getPlot().getDataModel() ;

        //seFirer.updateCtgMemberIds(ctgs);
        AppDataModel curModel=Summarizer.getCategorySummary(dataModel,ctgs);
        this.clearSelection() ;
        this.updateServiceDataModel(curModel);

        this.setStopActOnMouse(false);// 
    }

    public int getMode() {
        return mode;
    }

    public boolean setMode(int summaryMode) {

        int oldMode=this.mode ;
        this.mode = summaryMode;
        boolean success=swichMode();
        if(!success)
            this.mode =oldMode;
        return success;

    }



    public HashSet getCopyOfLastSelection() {
        return (HashSet) lastSelection.clone() ;//shallow copy. Element are Integer type, don't need copy
    }
    public HashSet getLastSelection() {
        return lastSelection ;//shallow copy. Element are Integer type, don't need copy
    }

    public void setLastSelection(HashSet lastSelection) {
        this.lastSelection = (HashSet) lastSelection.clone() ; // Since elements is type of Integer, shallow copy is enough
    }
    private void addToLastSelection(Integer i){
        lastSelection.add(i);
    }

    /*******************************************************************************************************
     *                 properties
     *******************************************************************************************************/
    /**
     * change lines' color to matach to colors of other components
     *  
     * Assumuption: the components share the same data
     * @param colors
     */
    public void setDataLayerColors(Color[] colors) {
       /* if(this.getMode() ==MODE_Normal){  //Assumuption: the components share the same data. Thus only work for NormalMode
            DataLayer srLayer=(DataLayer)this.ui.getLayer(Layer.ShowRangeData_LAYER );
            DataLayer dataLayer=(DataLayer)this.ui.getLayer(Layer.AllData_LAYER );
            if(srLayer!=null){//=null when reload a different data todo check why
                srLayer.setColors(colors);
                srLayer.repaintLayer() ;
            }
            if(dataLayer!=null){// =null when reload a different data
                dataLayer.setColors(colors);
                dataLayer.repaintLayer() ;
            }
            this.repaintPlot() ;
        }*/
        this.mactor.setDataLayerColors(colors);

    }

    public boolean isNeedClearSelection() {
        return needClearSelection;
    }

    public void setNeedClearSelection(boolean needClearSelection) {
        this.needClearSelection = needClearSelection;
    }
     public boolean isSmartHighlight() {
        return smartHighlight;
    }

    public void setSmartHighlight(boolean smartHighlight) {
        this.smartHighlight = smartHighlight;
    }

    public void setHighlightId(int highlightId) {
        //System.out.println("setHighlightId:"+highlightId);
        this.highlightId = highlightId;
    }


    public int getHighlightId() {
            return highlightId;
        }

    public boolean isHightLightMode() {
        return hightLightMode;
    }

      public boolean isStopActOnMouse() {
        return stopActOnMouse;
    }

    public void setStopActOnMouse(boolean stopActOnMouse) {
        this.stopActOnMouse = stopActOnMouse;
    }

    /*******************************************************************************************************
     *                inner classes
     *******************************************************************************************************/
    class ColorArrayEventFirer{
        private DataIDGroup grpData;

        public Color[] getRecordColor() {
            /* removed from esvc
            JVisualClassifier vc=DataSelectTool.this.getUI().getPlot().getVc();
            CategoryList ctgList=vc.getCtgList();
            Color[] colors=ctgList.getDataColors() ;*/
            DfPcp plot = (DfPcp) DataSelectTool.this.getUI().getPlot();
            //ESVisualClassifier vc = plot.getVc();
            //Color[] colors = vc.findDataColors();//esvc:replace old code
           /* if(colors==null||colors.length ==0){
                colors=plot.getDatacolors() ;
            }*/
            Color[] colors=plot.getDatacolors() ;
            if(getMode()==MODE_ObservationSummary ){

                return colors;
            }

            else{  //(getMode()==MODE_CategorySummary||getMode() getMode()==MODE_Normal){
                //DataSelectCmd.this.getUI().getPlot().fireColorArrayChanged(colors);
                return colors;
            }
        }


        private void fireColorArrayEvent() {
            Color[] colors=this.getRecordColor() ;
            DataSelectTool.this.getUI().getPlot().fireColorArrayChanged(colors);


        }
        public void setGrpData(DataIDGroup grpData) {
            this.grpData = grpData;
        }





    }



}
